#!/bin/sh
# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

# Log all calls, with arguments, to /var/log/messages
logger "$0" "$@"

XINPUT=/usr/bin/xinput

PROG=`basename $0`
COMMAND=$1

SCREEN_DPI=125

. $(dirname "$0")/common

export DISPLAY=:0
export XAUTHORITY=/home/chronos/.Xauthority

# On disk preferences
PREF_SENS_FILE=/home/chronos/user/mouse_sensitivity
PREF_SENS_VALID="[12345]"
PREF_SENS_DEFAULT=3
PREF_SWAP_LR_FILE=/home/chronos/user/mouse_swap_lr_buttons
PREF_SWAP_LR_VALID="[01]"
PREF_SWAP_LR_DEFAULT=0

# We are supposed to set an xinput setting as screen DPI * a multiplier that
# describes how fast the mouse should be (user preference). This function
# returns that product.
# We use multipliers of: 1, 1.4, 1.8, 2, 2.2. The low three have a slope that
# stops rising after speeds of 5 in/sec (hence 500 hundredths of an inch/sec
# param). See xorg-server's dix/ptrveloc.c for more info on the acceleration
# algorithm.
thresh_and_dpi_times_sensitivity_multiplier() {
  local sens=$1  # 1..5
  case $sens in
    1) echo 500 $SCREEN_DPI;;
    2) echo 500 $(((SCREEN_DPI * 7) / 5));;
    3) echo 500 $(((SCREEN_DPI * 9) / 5));;
    4) echo   0 $((SCREEN_DPI * 2));;
    5) echo   0 $(((SCREEN_DPI * 11) / 5));;
  esac
}

# Applies the given sensitivity preference (1..5) to all mice attached to the
# system. If this is called when a new device is being connected, that new
# device probably won't show up yet, so this won't apply to it.
apply_sensitivity() {
  local sens="$1"
  local mice="$2"
  local thresh_and_acc="$(thresh_and_dpi_times_sensitivity_multiplier "$sens")"
  if [ -z "$thresh_and_acc" ]; then
    return
  fi
  for mouse in $mice ; do
    if is_multitouch_mouse ${mouse}; then
      $XINPUT set-prop ${mouse} "Pointer Sensitivity" "${sens}"
    else
      $XINPUT set-ptr-feedback $mouse $thresh_and_acc 1
    fi
  done
}

# Applies the preference for whether or not to swap the left and right mouse
# buttons on all existing mice attached to the system. If this is called when a
# new device is being connected, that new device probably won't show up yet, so
# this won't apply to it.
apply_swap_lr() {
  local value="$1"
  local mice="$2"
  local map="1 2 3"
  if [ "$value" -eq 1 ]; then
    map="3 2 1"
  fi
  for mouse in $mice ; do
    $XINPUT get-button-map $mouse | sed "s/^[0-9]* [0-9]* [0-9]*/$map/" | \
        xargs $XINPUT set-button-map $mouse
  done
}

# Remaps buttons for a device
apply_button_remap() {
  local xinput_ids="$1"

  for xinput_id in $xinput_ids ; do
    local vp="$(vendor_product_for_xinput_id $xinput_id)"
    # 0304: USB Mighty Mouse, 030c: BT Mighty Mouse
    if [ "$vp" = "05ac:0304" -o "$vp" = "05ac:030c" ]; then
      $XINPUT get-button-map $xinput_id | awk \
          '{ for (i=1; i <= NF; i++) { \
               if (i == 8) { printf 0 } else { printf $(i) }; printf " " \
             } } END{ print "" }' | \
          xargs $XINPUT set-button-map $xinput_id
    fi
  done
}

apply_cpi_for_mouse() {
  local mouse="$1"
  if is_multitouch_mouse $1; then
    $XINPUT set-prop $mouse "Mouse CPI" $(get_cpi $mouse)
    $XINPUT set-prop $mouse "Device Accel Profile" -1
    $XINPUT set-prop $mouse "Device Accel Constant Deceleration" 1
    $XINPUT set-prop $mouse "Device Accel Velocity Scaling" 1
  else
    $XINPUT set-prop $mouse "Device Accel Constant Deceleration" \
        $(get_cpi $mouse)
    $XINPUT set-prop $mouse "Device Accel Velocity Scaling" 1
  fi
}

# Applies the correct CPI to the given mice
apply_cpi() {
  local mice="$1"
  for mouse in $mice ; do
    apply_cpi_for_mouse $mouse
  done
}

# Prints out all xinput IDs for all existing attached mice. If this is called
# when a new device is being connected, that new device probably won't show up
# yet, so this won't apply to it.
find_mice() {
  # Search for an xinput entry that contains the "slave pointer"
  # Then, find a field that matches "id=".
  # Lastly, return the part of the field after the '='.
  local slave_pointers="$($XINPUT list | \
    awk '/slave *pointer/ { for (i=1; i<NF; i++) \
    if ($i ~/id=/) print substr($i, 4) }')"
  # Next, return only those slave pointers that are using the Evdev driver
  for pointer in $slave_pointers ; do
    local match=$($XINPUT list-props $pointer | grep "Evdev" | wc -l)
    if [ "$match" != "0" ]; then
      echo -n " $pointer"
    elif is_multitouch_mouse ${pointer}; then
      echo -n " ${pointer}"
    fi
  done
}

# WARNING: This table is now deprecated. If you want to add new entries to it,
# please instead add a new section in the 20-mouse.conf file in the xorg-conf
# package.
#
# Returns the CPI (counts per inch) of the newly attached mouse. Since mice
# generally don't report CPI, we use the following approach: By default, we
# assume a CPI of 1000, as this is quite close to most mice on the market today
# (2012 time frame). For mice that we know aren't near 1000, we have a database
# of CPI values.
get_cpi() {
  local xinput_id="$1"

  local vp="$(vendor_product_for_xinput_id $xinput_id)"

  # Database of mice that have CPI (DPI) significantly off from 1000.
  local cpi=1000;  # Default CPI
  local trackpoint_cpi=400;  # Approximate, since CPI doesn't exactly apply
  case $vp in
    "0002:000a") cpi=$trackpoint_cpi;;  # PS/2 IBM Trackpoint
    "045e:0024") cpi=400;;  # Microsoft Corp. Trackball Explorer*
    "045e:0040") cpi=416;;  # Microsoft Corp. Wheel Mouse Optical
    "0461:4d22") cpi=474;;  # Standard Dell
    "046d:1028") cpi=600;;  # M570 trackball[u]
    "046d:400e") cpi=400;;  # K400 touchpad[u]
    "046d:4024") cpi=400;;  # K400r touchpad[u]
    "046d:c00f") cpi=385;;  # Logitech MouseMan Traveler/Mobile
    "046d:c00"*) cpi=385;;  # Old Logitech Mice (copying 0xc00f setting)
    "046d:c014") cpi=425;;  # HP branded "Logitech Optical USB Mouse"
    "046d:c016") cpi=377;;  # HP branded "Logitech, Inc. Optical Wheel Mouse"
    "046d:c018") cpi=530;;  # "Logitech, Inc. Optical Wheel Mouse" model M-UAE96
    "046d:c03d") cpi=434;;  # Logitech M-BT96a Pilot Optical Mouse
    "046d:c03e") cpi=464;;  # Logitech Premium Optical Wheel Mouse (M-BT58)
    "046d:c077") cpi=580;;  # HP branded "Logitech USB Optical Mouse"
    # According to http://www.linux-usb.org/usb.ids , these are 4 trackballs:
    "046d:c40"*) cpi=300;;  # Logitech Trackballs*
    "046d:c508") cpi=300;;  # Cordless Trackball
    "047d:1002") cpi=300;;  # Kensington Turbo Mouse Pro (trackball)*
    "047d:1003") cpi=300;;  # Kensington Orbit TrackBall (trackball)*
    "047d:1005") cpi=300;;  # Kensington TurboBall (trackball)*
    "047d:1006") cpi=300;;  # Kensington TurboRing (trackball)*
    "047d:1009") cpi=300;;  # Kensington Orbit TrackBall for Mac (trackball)*
    "047d:1020") cpi=300;;  # Kensington Expert Mouse (trackball)*
    "047d:2041") cpi=300;;  # Kensington SlimBlade Trackball (trackball)*
    "04d9:2519") cpi=400;;  # FAVI Wireless Keyboard (TouchPad)*
    "05ac:0304") cpi=400;;  # Apple USB Optical Mouse (Mighty Mouse)
    "05ac:030c") cpi=400;;  # Apple BT Optical Mouse (Mighty Mouse)
    "05ac:030d") cpi=1780;; # Apple magicmouse (BT)
    "05ac:"*)    cpi=373;;  # Apple mice (other)
    "06cb:0009") cpi=$trackpoint_cpi;;  # USB IBM Trackpoint
    "0a5c:8502") cpi=400;;  # FAVI Wireless Keyboard (TouchPad), Bluetooth*
    "0c45:7000") cpi=400;;  # FAVI Entertainment Wireless Keyboard (TouchPad)*
    "1532:0016") cpi=1714;; # Razer USA, Ltd DeathAdder RZ01-0015
    "17ef:6009") cpi=$trackpoint_cpi;;  # Lenovo ThinkPad Keyboard w/ TrackPoint
    "17ef:6014") cpi=400;;  # Lenovo N5901 multimedia keyboard/trackball*
    "17ef:602b") cpi=400;;  # Lenovo N5902 multimedia keyboard/OFN*
    "1997:0409") cpi=400;;  # Riitek Rii Mote i6 (TouchPad)*
    "413c:3012") cpi=502;;  # Dell Computer Corp. Optical Wheel Mouse
  esac
  # * Trackball/touchpad measurements are approximate
  # [u] This device uses the unifying receiver
  echo $cpi
}

# Kernel's Apple magicmouse driver by default will interpret mouse events, and
# we would like to turn off this interpretation.
fixup_apple_magicmouse_driver_parameters() {
  if [ "$(vendor_product_for_xinput_id $1)" != "05ac:030d" ]; then
    return
  fi
  echo 0 > /sys/module/hid_magicmouse/parameters/emulate_3button
  echo 0 > /sys/module/hid_magicmouse/parameters/emulate_scroll_wheel
}

# Add is a special command that's run possibly before xinput detects the mice.
# It's run from udev. We handle this here, rather than below, since a newly
# plugged device may not show up in xinput yet.
if [ "$COMMAND" = "add" ]; then
  newid=$(wait_and_get_added_xinput_id)
  if [ -z "$newid" ]; then
    return
  fi
  apply_cpi_for_mouse $newid

  sens=$(load_pref SENS)
  apply_sensitivity $sens "$newid"
  swap_lr=$(load_pref SWAP_LR)
  apply_swap_lr $swap_lr "$newid"
  apply_button_remap "$newid"

  fixup_apple_magicmouse_driver_parameters "$newid"

  exit 0
fi

usage() {
  echo "Usage:" \
    "$PROG {listdev|add|status|sensitivity [1-5]|swap_left_right" \
    "[01]|set property value}"
}

MICE=$(find_mice)
if [ -z "$MICE" ]; then
  exit 0
fi
VALID=0

# If no args passed, print usage
if [ $# = 0 ] ; then
  usage
  exit 1
fi

while true ; do
  COMMAND=$1
  VALID=0

  if [ $# = 0 ] ; then
    # If no commands remain, we're done
    exit 0
  elif [ "$COMMAND" = "listdev" ] ; then
    for mouse in $MICE ; do
      echo "${mouse}"
    done
    VALID=1
    shift
  elif [ "$COMMAND" = "status" ] ; then
    for mouse in $MICE ; do
      echo "Details for mouse ${mouse}:"
      $XINPUT list-props $mouse
      $XINPUT get-feedbacks $mouse
    done
    VALID=1
    shift
  elif [ "$COMMAND" = "sensitivity" -a -n "$2" ] ; then
    sens="$2"
    store_pref SENS "$sens"
    apply_sensitivity "$sens" "$MICE"
    VALID=1
    shift 2
  elif [ "$COMMAND" = "swap_left_right" -a -n "$2" ] ; then
    value="$2"
    store_pref SWAP_LR "$value"
    apply_swap_lr "$value" "$MICE"
    VALID=1
    shift 2
  elif [ "$COMMAND" = "refresh" ] ; then
    apply_cpi "$MICE"
    apply_button_remap "$MICE"
    apply_sensitivity $(load_pref SENS) "$MICE"
    apply_swap_lr $(load_pref SWAP_LR) "$MICE"
    VALID=1
    shift
  elif [ "$COMMAND" = "set" -a -n "$2" -a -n "$3" ] ; then
    prop=$2
    value=$3
    for mouse in $MICE ; do
      $XINPUT set-prop $mouse "$prop" "$value"
    done
    VALID=1
    shift 3
  fi

  if [ $VALID = 0 ] ; then
    usage
    exit 1
  fi
done
